package panda.server.util.imageprocessing;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;

import panda.client.enumerations.CardScale;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

/**
 * Contains utility methods for processing HQ card scans into cardpanda scan format.
 *
 * @author zane.tian@gmail.com
 */
public class CardImageUtility
{
	static FileOutputStream fos;
	static JPEGImageEncoder jpeg;

	static BufferedImage bimg;
	final static long appStartTime = System.currentTimeMillis();

	public static void main(String[] args) throws Exception
	{
		System.err.println("Loading at " + (System.currentTimeMillis() - appStartTime) + "ms");
		
		batchResize();
		// recursiveCropRoundedBorder();

		// cropGathererV2Images("C:/Users/kyruru/Desktop/roe-tokens/", "C:/Users/kyruru/Desktop/roe-cropped/");
		// batchShiftAndBlackBorder();
		// recursivelyBlackBorder();

		System.err.println("Done in: " + (System.currentTimeMillis() - appStartTime) + "ms");
	}

	/**
	 * Expects a folder named srcFolder with children folders named after magicSet abbreviations. These children folders
	 * contain high quality images numbered by ID.<br>
	 * <br>
	 * src<br>
	 * ---mt<br>
	 * ------1.jpg<br>
	 * ------2.jpg<br>
	 * ------...<br>
	 * ------180.jpg<br>
	 * ---lw<br>
	 * ---shm<br>
	 * <br>
	 * Processes all HQ scans and outputs them in cardpanda format with borders: 25-tapped, 25, 50, 75, 100
	 */
	public static void batchResize()
	{
		String workArea = "C:/Users/kyruru/Desktop"; /* usually desktop */
		String srcFolderName = "src";
		String outputFolderName = "done";

		/* create the output directory root */
		File outputRoot = new File(workArea + "\\" + outputFolderName);
		outputRoot.mkdir();

		/* get children folders of the source directory */
		File srcDirectory = new File(workArea + "\\" + srcFolderName);
		String[] srcFolderChildren = srcDirectory.list();

		try
		{
			/* loop through the magicSet children folders in the source directory */
			for (String magicSet : srcFolderChildren)
			{
				File magicSetDir = new File(workArea + "\\" + srcFolderName + "\\" + magicSet);
				System.out.println(magicSetDir.getAbsolutePath());

				/* create inner output directory structure */
				File outputDirWithSet = new File(outputRoot.getPath() + "\\" + magicSet);
				outputDirWithSet.mkdir();

				if (!new File(outputDirWithSet.getAbsolutePath() + "\\25percent").mkdir()
						|| !new File(outputDirWithSet.getAbsolutePath() + "\\25percent\\tapped").mkdir()
						|| !new File(outputDirWithSet.getAbsolutePath() + "\\50percent").mkdir()
						|| !new File(outputDirWithSet.getAbsolutePath() + "\\75percent").mkdir()
						|| !new File(outputDirWithSet.getAbsolutePath() + "\\100percent").mkdir())
				{
					throw new IllegalStateException("Output directory structure already exists!");
				}

				/* loop through the images within each magicSet directory */
				String[] imgFiles = magicSetDir.list();
				for (String imgName : imgFiles)
				{
					File rawImage = new File(workArea + "\\" + srcFolderName + "\\" + magicSet + "\\" + imgName);
					System.out.println("processing " + rawImage.getCanonicalPath());

					/* for each cardscale, create a black bordered, scaled version in the output directory */
					for (CardScale scale : CardScale.values())
					{
						File outputImage;
						String scaleFolderName = ""; /* 25percent, 25percent/tapped, 50percent, 75 percent, 100percent */

						switch (scale)
						{
							case FULL:
								scaleFolderName = "100percent";
								break;
							case THREE_QUARTER:
								scaleFolderName = "75percent";
								break;
							case HALF:
								scaleFolderName = "50percent";
								break;
							case QUARTER:
								scaleFolderName = "25percent";
								break;
							default:
								throw new IllegalStateException("There should not be any other matches for CardScale: " + scale);
						}

						outputImage = new File(outputDirWithSet + "\\" + scaleFolderName + "\\" + imgName);
						resize(rawImage, outputImage, scale.getWidth(), scale.getHeight(), 1f);

						/* create the a tapped image (rotated 90 degrees clockwise) for CardScale.QUARTER */
						if (scale.equals(CardScale.QUARTER))
						{
							BufferedImage bimg = ImageIO.read(outputImage);
							fos = new FileOutputStream(outputDirWithSet + "\\25percent\\tapped\\" + imgName);
							jpeg = JPEGCodec.createJPEGEncoder(fos);
							jpeg.encode(rotateClockWise(bimg));
							fos.close();
						}
					}
				}
			}
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}

	/**
	 * -smoothly- resizes a BufferedImage.
	 */
	public static void resize(File originalFile, File resizedFile, int newWidth, int newHeight, float quality)
			throws IOException
	{

		if (quality < 0 || quality > 1)
		{
			throw new IllegalArgumentException("Quality has to be between 0 and 1");
		}

		ImageIcon ii = new ImageIcon(originalFile.getCanonicalPath());
		Image i = ii.getImage();
		Image resizedImage = null;

		resizedImage = i.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH);

		/* This code ensures that all the pixels in the image are loaded. */
		Image temp = new ImageIcon(resizedImage).getImage();

		/* Create the buffered image */
		BufferedImage bufferedImage = new BufferedImage(temp.getWidth(null), temp.getHeight(null),
				BufferedImage.TYPE_INT_RGB);

		/* Copy image to buffered image. */
		Graphics g = bufferedImage.createGraphics();

		/* Clear background and paint the image. */
		g.setColor(Color.white);
		g.fillRect(0, 0, temp.getWidth(null), temp.getHeight(null));
		g.drawImage(temp, 0, 0, null);
		g.dispose();

		/* add a 2px black border to overwrite the old bordering pixels */
		bufferedImage = blackBorder(bufferedImage);

		// Soften.
		// float softenFactor = 0.00f;
		// float[] softenArray = {0, softenFactor, 0, softenFactor, 1-(softenFactor*4), softenFactor, 0, softenFactor, 0};
		// Kernel kernel = new Kernel(3, 3, softenArray);
		// ConvolveOp cOp = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
		// bufferedImage = cOp.filter(bufferedImage, null);

		/* Write the jpeg to a file + encode image as a JPEG data stream */
		FileOutputStream out = new FileOutputStream(resizedFile);
		JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
		JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(bufferedImage);

		param.setQuality(quality, true);

		encoder.setJPEGEncodeParam(param);
		encoder.encode(bufferedImage);
	}

	@Deprecated
	/* make this act on an entire folder of magicsets instead of for each set */
	public static void recursivelyBlackBorder()
	{
		String desktop = "c:\\Documents and Settings\\kyruru\\Desktop";
		String set = "\\eve-tokens";
		String setDir = "\\noborder" + set;
		String outputDir = "\\bordered" + set;

		/* this is the expected file format */
		List<String> subDirs = new ArrayList<String>();
		subDirs.add("\\25percent");
		subDirs.add("\\25percent\\tapped");
		// subDirs.add("\\25percent\\180degrees");
		subDirs.add("\\50percent");
		subDirs.add("\\75percent");
		subDirs.add("\\100percent");

		/* loop through all the subdirectories, creating the new output dirs if they don't exist */
		for (String subdir : subDirs)
		{
			System.out.println(desktop + setDir + subdir);
			new File(desktop + outputDir + subdir).mkdirs();

			try
			{
				File dir = new File(desktop + setDir + subdir);

				String[] children = dir.list();
				if (children == null)
				{
					System.out.println("uh oh, " + dir + " is empty");
				}
				else
				{
					for (int i = 0; i < children.length; i++)
					{
						// Get filename of file or directory and read information from the file
						String filename = children[i];
						if (!filename.contains(".jpg"))
						{
							System.out.println("Filename doesn't contain .jpg: " + filename);
							continue;
						}
						System.out.println(filename);

						BufferedImage bimg = ImageIO.read(new File(dir + "\\" + filename));
						fos = new FileOutputStream(desktop + outputDir + subdir + "\\" + filename);
						jpeg = JPEGCodec.createJPEGEncoder(fos);
						jpeg.encode(blackBorder(bimg));
						fos.close();
					}
				}
			}
			catch (Exception ex)
			{
				ex.printStackTrace();
			}
		}
	}

	/**
	 * calls shiftAndBlackBorder on all children of dirString; used for low quality Gatherer imgs
	 */
	public static void batchShiftAndBlackBorder()
	{
		String dirString = "C:\\Documents and Settings\\kyruru\\Desktop\\100";

		try
		{
			File dir = new File(dirString);

			String[] children = dir.list();
			if (children == null)
			{
				System.out.println("uh oh, " + dir + " is empty");
			}
			else
			{
				for (int i = 0; i < children.length; i++)
				{
					// Get filename of file or directory and read information from the file
					String filename = children[i];
					if (!filename.contains(".jpg"))
					{
						System.out.println("Filename doesn't contain .jpg: " + filename);
						continue;
					}
					System.out.println(filename);

					BufferedImage bimg = ImageIO.read(new File(dir + "\\" + filename));
					fos = new FileOutputStream("C:\\Documents and Settings\\kyruru\\Desktop\\shifted\\" + filename);
					jpeg = JPEGCodec.createJPEGEncoder(fos);
					jpeg.encode(shiftAndBlackBorder(bimg));
					fos.close();
				}
			}

		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}

	/**
	 * calls cropRoundedBorder on a directory of files: but gatherer rounded images are just scaled shitty images...
	 * 
	 * NOTE: this method primarily takes magiccards.info's temporary images (before they get HQ scans).
	 * 	Temporary images from Gatherer are cropped (the border is too large as of July 2011) and resized to 312x445
	 * 		cropped images are output to dirString/shifted
	 * 		cropped and resized images are output to dirString/shiftedAndResized
	 */
	public static void recursiveCropRoundedBorder()
	{
		String dirString = "C:/users/kyruru/Desktop/m11";

		try
		{
			File dir = new File(dirString);

			String[] children = dir.list();
			if (children == null)
			{
				System.out.println("uh oh, " + dir + " is empty");
			}
			else
			{
				/* create the output directories */
				boolean cropOutputDirCreated = (new File(dirString + "/shifted")).mkdir();
				boolean cropAndResizeOutputDirCreated = (new File(dirString + "/shiftedAndResized")).mkdir();
				if(!(cropOutputDirCreated && cropAndResizeOutputDirCreated)) {
					throw new IllegalStateException("unable to create one or both of the output directories (or they already exist)");
				}
				
				for (int i = 0; i < children.length; i++)
				{
					// Get filename of file or directory and read information from the file
					String filename = children[i];
					if (!filename.contains(".jpg"))
					{
						System.out.println("Filename doesn't contain .jpg: " + filename);
						continue;
					}
					System.out.println(filename);
					

					BufferedImage bimg = ImageIO.read(new File(dir + "/" + filename));
					fos = new FileOutputStream(dirString + "/shifted/" + filename);
					jpeg = JPEGCodec.createJPEGEncoder(fos);
					jpeg.encode(cropRoundedBorder(bimg));
					fos.close();
					
					/* add this if you want to resize */
					File originalImage = new File(dirString + "/shifted/" + filename);
					File outputImage = new File(dirString + "/shiftedAndResized/" + filename);
					resize(originalImage, outputImage, 312, 445, 1f);
				}
			}

		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}

	/**
	 * Takes a 265 x 370 rounded black border screenshot from Wizards Visual Spoiler and chops off part of the border.
	 */
	public static BufferedImage cropRoundedBorder(BufferedImage bi) throws IOException
	{
		/* HQ bordered Wizards.com preview pictures are 350 x 489 */
		/* Wizards visual spoiler pictures are 265 x 370 */
		// final int width = 265;
		// final int height = 370;
		final int width = bi.getWidth();
		final int height = bi.getHeight();

		final int borderChopWidth = 4;

		/* create a smaller image */
		BufferedImage bufferedImage = new BufferedImage(width - 2 * borderChopWidth, height - 2 * borderChopWidth, BufferedImage.TYPE_INT_RGB);

		/* copy smaller input image into the result image at an offset */
		for (int y = 0; y < bufferedImage.getHeight(); y++)
		{
			for (int x = 0; x < bufferedImage.getWidth(); x++)
			{
				bufferedImage.setRGB(x, y, bi.getRGB(x + borderChopWidth, y + borderChopWidth));
			}
		}
		
		return bufferedImage;
	}

	/**
	 * 2009-04-25
	 * Crops the border of a gatherer-version2 card image. The original input images are 223x310, including the fat
	 * rounded border.
	 *
	 * @return A copy of a portion of a gatherer-version2 image with the borders cropped to a certain degree, resulting
	 *         in a rectangular image.
	 */
	public static BufferedImage cropRoundedBorderFromGathererV2(BufferedImage bi) throws IOException
	{
		final int width = bi.getWidth();
		final int height = bi.getHeight();
		final int borderChopWidth = 6; /* how many pixels from each border to be removed */

		BufferedImage biResult = new BufferedImage(width - 2 * borderChopWidth, height - 2 * borderChopWidth, BufferedImage.TYPE_INT_RGB);

		/* copy smaller input image into the result image at an offset */
		for (int y = 0; y < biResult.getHeight(); y++)
		{
			for (int x = 0; x < biResult.getWidth(); x++)
			{
				biResult.setRGB(x, y, bi.getRGB(x + borderChopWidth, y + borderChopWidth));
			}
		}

		return biResult;
	}

	/**
	 * 2009-04-25
	 * Takes a directory of raw gatherer-version2 images and partially crops their borders to resemble magiccards.info
	 * scans. Needs an output directory already created.
	 *
	 * @param sPath The directory containing raw gatherer-version2 .jpg images.
	 */
	public static void cropGathererV2Images(String sPath, String sOutPath)
	{
		try
		{
			File dir = new File(sPath);

			String[] children = dir.list();
			if (children == null)
			{
				System.out.println("uh oh, " + dir + " is empty");
			}
			else
			{
				for (int i = 0; i < children.length; i++)
				{
					// Get filename of file or directory and read information from the file
					String filename = children[i];
					if (!filename.contains(".jpg"))
					{
						System.out.println("Filename doesn't contain .jpg: " + filename);
						continue;
					}
					System.out.println(filename);

					BufferedImage bimg = ImageIO.read(new File(dir + "\\" + filename));
					fos = new FileOutputStream(sOutPath + filename);
					jpeg = JPEGCodec.createJPEGEncoder(fos);
					jpeg.encode(cropRoundedBorderFromGathererV2(bimg));
					fos.close();
				}
			}

		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}

	/**
	 * Used for gatherer images which are 200x285. Adds a 4 pixel black border, enlarging image to 208x293
	 *
	 * @param bi Image to be enlarged and black bordered
	 *
	 * @return
	 *
	 * @throws IOException
	 */
	private static BufferedImage shiftAndBlackBorder(BufferedImage bi) throws IOException
	{
		/* create a larger image */
		BufferedImage bufferedImage = new BufferedImage(312, 445, BufferedImage.TYPE_INT_RGB);

		/* copy smaller image bi into the larger image at an offset */
		for (int y = 0; y < bi.getHeight() - 1; y++)
		{
			for (int x = 0; x < bi.getWidth() - 1; x++)
			{
				bufferedImage.setRGB(x + 6, y + 6, bi.getRGB(x, y));
			}
		}

		return bufferedImage;
	}

	/**
	 * overwrites a 2px black border on a BufferedImage
	 */
	private static BufferedImage blackBorder(BufferedImage bi) throws IOException
	{

		int width = bi.getWidth();
		int height = bi.getHeight();

		BufferedImage bordered = bi;

		for (int i = 0; i < width; i++)
		{
			bordered.setRGB(i, 0, 0);
			bordered.setRGB(i, 1, 0);
			bordered.setRGB(i, height - 1, 0);
			bordered.setRGB(i, height - 2, 0);
		}

		for (int j = 0; j < height; j++)
		{
			bordered.setRGB(0, j, 0);
			bordered.setRGB(1, j, 0);
			bordered.setRGB(width - 1, j, 0);
			bordered.setRGB(width - 2, j, 0);
		}

		return bordered;
	}

	private static BufferedImage rotateClockWise(BufferedImage bi)
	{
		int width = bi.getWidth();
		int height = bi.getHeight();

		BufferedImage biFlip = new BufferedImage(height, width, bi.getType());

		for (int i = 0; i < width; i++)
		{
			for (int j = 0; j < height; j++)
			{
				biFlip.setRGB(height - j - 1, i, bi.getRGB(i, j));
			}
		}

		return biFlip;
	}

	public static BufferedImage rotateCounterClockWise(BufferedImage bi)
	{
		int width = bi.getWidth();
		int height = bi.getHeight();

		BufferedImage biFlip = new BufferedImage(height, width, bi.getType());

		for (int i = 0; i < width; i++)
		{
			for (int j = 0; j < height; j++)
			{
				biFlip.setRGB(j, width - 1 - i, bi.getRGB(i, j));
			}
		}

		return biFlip;
	}
}